#ifndef _SELECT_LOOP_H_
#define _SELECT_LOOP_H_

#include <assert.h>
#include <vector>

#include "../Log.h"

using namespace std;

class SocketHandler {
	public:	virtual void operator()(void *, struct sockaddr *) = 0;
	virtual ~SocketHandler() { }
};

class TimeoutHandler {
	public:	virtual void operator()() = 0;
	virtual ~TimeoutHandler() { }
};

/*	Creating a SelectLoop makes it easy to listen on a socket and execute code when data is received. It also
	allows you to wake up on intervals you specify and dispatch accordingly.
*/
class SelectLoop
{	
	struct ListenSocket
	{
		int sock;
		int max_len;
		
		SocketHandler *handle;
		
		ListenSocket(int sock, int max_len, SocketHandler *handle);
		ListenSocket(const ListenSocket&);
		ListenSocket& operator=(const ListenSocket&) { return *this; }
	};
	
	struct WakeupInterval
	{
		struct timeval interval;
		struct timeval last_wakeup;
		struct timeval time_to_next_wakeup;
		
		TimeoutHandler *handle;
		
		WakeupInterval(unsigned int ms, TimeoutHandler *t);
		WakeupInterval(const WakeupInterval&);
		WakeupInterval& operator=(const WakeupInterval&) { return *this; }
	};
	
	vector <WakeupInterval> wakeups;
	vector <ListenSocket> sockets;
	
	int max_fd;
	int max_max_len;
	
	struct timeval zero, wakeup;
	struct sockaddr_in from;
	socklen_t fromlen;
	void *buf;		// The buffer we recv into. It's malloc'd to an appropriate size in add_socket
	
	// Computes the difference between tv1 and tv2 (tv2 - tv1). The difference	will be negative if tv1 is larger than tv2.
	struct timeval tv_difference(struct timeval tv1, struct timeval tv2);
	
	//	Returns -1 if tv1 < tv2, 0 if tv1 == tv2, 1 if tv1 > tv2
	int tv_compare(struct timeval tv1, struct timeval tv2);
	
	// Returns the min of tv1 and tv2
	struct timeval tv_min(struct timeval tv1, struct timeval tv2);
	
	// Returns the max of tv1 and tv2
	struct timeval tv_max(struct timeval tv1, struct timeval tv2);

public:
	SelectLoop();
	
	// Timeout, and what to do on each timeout (the timeout takes the shared data). Returns an identifier for this wakeup interval.
	unsigned int add_wakeup_interval(unsigned int ms, TimeoutHandler *handle);
	
	// Resets the specified wakeup interval so that it won't wake up for another full interval
	void reset_wakeup_interval(unsigned int identifier);
	
	/*	Which socket to listen to, how much data might be recv'd, and the callback (received data, and who it came from). Returns
		a unique identifier for this socket handler.
	*/
	unsigned int add_socket(int sock, int max_len, SocketHandler *handle);
	
	void run();
};

#endif
